This report analyzes temperature trends across Egyptian governorates from 1960 to 2024 using ERA5 reanalysis data. Key findings include:
Population weighting provides a more accurate representation of human exposure to temperature changes.
libs <- c("tidyverse", "here", "lubridate", "plotly", "scales", "knitr", "DT")
installed_libs <- libs %in% rownames(installed.packages())
if (any(installed_libs == FALSE)) {
pak::pkg_install(libs[!installed_libs])
}
invisible(lapply(libs, library, character.only = TRUE))
# Load temperature data
era5_temp_df <- readRDS(file = paste(here(), "Data", "intermediate",
"Governorate Data",
"era5_temp_19602024.Rds", sep = "/"))
# Create annual data
era5_temp_annual <- era5_temp_df |>
group_by(name, year) |>
summarise(
# Unweighted means
annual_mean_temp = mean(mean_temp, na.rm = TRUE),
annual_mean_maxtemp = mean(mean_maxtemp, na.rm = TRUE),
annual_mean_mintemp = mean(mean_mintemp, na.rm = TRUE),
# Population-weighted means
annual_mean_temp_pop = mean(mean_temp_pop_weighted, na.rm = TRUE),
annual_mean_maxtemp_pop = mean(mean_maxtemp_pop_weighted, na.rm = TRUE),
annual_mean_mintemp_pop = mean(mean_mintemp_pop_weighted, na.rm = TRUE),
.groups = "drop"
)
# Calculate baseline statistics (1960-2010)
baseline_stats <- era5_temp_annual |>
filter(year <= 2010) |>
group_by(name) |>
summarise(
# Unweighted baselines
mean_temp_baseline = mean(annual_mean_temp, na.rm = TRUE),
sd_temp_baseline = sd(annual_mean_temp, na.rm = TRUE),
mean_maxtemp_baseline = mean(annual_mean_maxtemp, na.rm = TRUE),
sd_maxtemp_baseline = sd(annual_mean_maxtemp, na.rm = TRUE),
mean_mintemp_baseline = mean(annual_mean_mintemp, na.rm = TRUE),
sd_mintemp_baseline = sd(annual_mean_mintemp, na.rm = TRUE),
# Population-weighted baselines
mean_temp_pop_baseline = mean(annual_mean_temp_pop, na.rm = TRUE),
sd_temp_pop_baseline = sd(annual_mean_temp_pop, na.rm = TRUE),
mean_maxtemp_pop_baseline = mean(annual_mean_maxtemp_pop, na.rm = TRUE),
sd_maxtemp_pop_baseline = sd(annual_mean_maxtemp_pop, na.rm = TRUE),
mean_mintemp_pop_baseline = mean(annual_mean_mintemp_pop, na.rm = TRUE),
sd_mintemp_pop_baseline = sd(annual_mean_mintemp_pop, na.rm = TRUE),
.groups = "drop"
)
# Join and calculate deviations
era5_temp_annual <- era5_temp_annual |>
left_join(baseline_stats, by = "name") |>
mutate(
# Unweighted deviations
temp_diff_mean = annual_mean_temp - mean_temp_baseline,
temp_diff_max = annual_mean_maxtemp - mean_maxtemp_baseline,
temp_diff_min = annual_mean_mintemp - mean_mintemp_baseline,
z_score_mean = (annual_mean_temp - mean_temp_baseline) / sd_temp_baseline,
z_score_max = (annual_mean_maxtemp - mean_maxtemp_baseline) / sd_maxtemp_baseline,
z_score_min = (annual_mean_mintemp - mean_mintemp_baseline) / sd_mintemp_baseline,
# Population-weighted deviations
temp_diff_mean_pop = annual_mean_temp_pop - mean_temp_pop_baseline,
temp_diff_max_pop = annual_mean_maxtemp_pop - mean_maxtemp_pop_baseline,
temp_diff_min_pop = annual_mean_mintemp_pop - mean_mintemp_pop_baseline,
z_score_mean_pop = (annual_mean_temp_pop - mean_temp_pop_baseline) / sd_temp_pop_baseline,
z_score_max_pop = (annual_mean_maxtemp_pop - mean_maxtemp_pop_baseline) / sd_maxtemp_pop_baseline,
z_score_min_pop = (annual_mean_mintemp_pop - mean_mintemp_pop_baseline) / sd_mintemp_pop_baseline
)
The plot below shows how mean temperatures have deviated from the 1960-2010 baseline for major governorates. Red bars indicate years warmer than the baseline, while blue bars indicate cooler years.
# Select key governorates for visualization
key_govs <- c("Cairo", "Alexandria", "Assiut", "Suhag")
p_mean_dev <- era5_temp_annual |>
filter(name %in% key_govs) |>
ggplot(aes(x = year, y = temp_diff_mean_pop, fill = temp_diff_mean_pop)) +
geom_col(color = "black", width = 0.8, alpha = 0.9) +
facet_wrap(~name, ncol = 2) +
scale_fill_gradient2(
low = "#0571b0",
mid = "white",
high = "#ca0020",
midpoint = 0,
name = "Deviation (°C)"
) +
scale_x_continuous(breaks = seq(1960, 2024, by = 10)) +
theme_minimal(base_size = 12) +
labs(
title = "Mean Temperature - Deviation from Baseline (Population-weighted)",
subtitle = "Relative to 1960-2010 average - ERA5 data",
x = "Year",
y = "Temperature Deviation (°C)"
) +
theme(
legend.position = "bottom",
strip.background = element_rect(fill = "lightgray", color = NA),
strip.text = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
ggplotly(p_mean_dev, tooltip = c("x", "y")) |>
layout(hovermode = "closest")
This comparison shows how population weighting affects temperature measurements. Differences highlight areas where population centers experience different conditions than the governorate average.
# Create comparison data
df_compare <- era5_temp_annual |>
filter(name %in% key_govs) |>
select(name, year,
Unweighted = temp_diff_mean,
`Population-weighted` = temp_diff_mean_pop) |>
pivot_longer(cols = c(Unweighted, `Population-weighted`),
names_to = "Weighting", values_to = "value")
p_compare <- ggplot(df_compare, aes(x = year, y = value, color = Weighting, group = Weighting)) +
geom_line(linewidth = 1) +
geom_point(size = 1.5, alpha = 0.6) +
geom_smooth(method = "loess", se = TRUE, alpha = 0.2, linewidth = 0.8) +
facet_wrap(~name, ncol = 2) +
scale_color_manual(values = c("Unweighted" = "#E69F00",
"Population-weighted" = "#0072B2")) +
theme_minimal(base_size = 12) +
labs(
title = "Mean Temperature - Comparison of Weighting Methods",
subtitle = "Deviation from 1960-2010 baseline - ERA5 data",
x = "Year",
y = "Temperature Deviation (°C)"
) +
theme(
legend.position = "bottom",
strip.background = element_rect(fill = "lightgray", color = NA),
strip.text = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
ggplotly(p_compare, tooltip = c("x", "y", "colour")) |>
layout(hovermode = "closest")
This plot shows absolute annual mean temperatures (population-weighted) with trend lines.
p_absolute <- era5_temp_annual |>
filter(name %in% key_govs) |>
ggplot(aes(x = year, y = annual_mean_temp_pop)) +
geom_line(color = "#0072B2", linewidth = 1) +
geom_point(color = "#0072B2", size = 2, alpha = 0.7) +
geom_smooth(method = "loess", se = TRUE, color = "#D55E00",
fill = "#D55E00", alpha = 0.2) +
facet_wrap(~name, ncol = 2) +
theme_minimal(base_size = 12) +
labs(
title = "Mean Temperature - Absolute Values (Population-weighted)",
subtitle = "Annual averages - ERA5 data",
x = "Year",
y = "Temperature (°C)"
) +
theme(
legend.position = "bottom",
strip.background = element_rect(fill = "lightgray", color = NA),
strip.text = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
ggplotly(p_absolute, tooltip = c("x", "y")) |>
layout(hovermode = "closest")
Maximum temperatures are particularly relevant for heat stress and health impacts.
p_max_dev <- era5_temp_annual |>
filter(name %in% key_govs) |>
ggplot(aes(x = year, y = temp_diff_max_pop, fill = temp_diff_max_pop)) +
geom_col(color = "black", width = 0.8, alpha = 0.9) +
facet_wrap(~name, ncol = 2) +
scale_fill_gradient2(
low = "#0571b0",
mid = "white",
high = "#ca0020",
midpoint = 0,
name = "Deviation (°C)"
) +
scale_x_continuous(breaks = seq(1960, 2024, by = 10)) +
theme_minimal(base_size = 12) +
labs(
title = "Maximum Temperature - Deviation from Baseline (Population-weighted)",
subtitle = "Relative to 1960-2010 average - ERA5 data",
x = "Year",
y = "Temperature Deviation (°C)"
) +
theme(
legend.position = "bottom",
strip.background = element_rect(fill = "lightgray", color = NA),
strip.text = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
ggplotly(p_max_dev, tooltip = c("x", "y")) |>
layout(hovermode = "closest")
summary_stats <- era5_temp_annual |>
filter(name %in% key_govs) |>
group_by(name) |>
summarise(
`Mean (°C)` = round(mean(annual_mean_temp_pop, na.rm = TRUE), 2),
`Max (°C)` = round(mean(annual_mean_maxtemp_pop, na.rm = TRUE), 2),
`Min (°C)` = round(mean(annual_mean_mintemp_pop, na.rm = TRUE), 2),
`Warming Trend (°C)` = round(
coef(lm(annual_mean_temp_pop ~ year))[2] * (max(year) - min(year)), 2
),
`Recent Decade Mean (2015-2024)` = round(
mean(annual_mean_temp_pop[year >= 2015], na.rm = TRUE), 2
),
.groups = "drop"
) |>
rename(Governorate = name)
datatable(
summary_stats,
options = list(pageLength = 10, dom = 't'),
caption = "Temperature statistics for the full period (1960-2024). Warming trend shows total temperature increase over the period."
)
decade_stats <- era5_temp_annual |>
filter(name %in% key_govs) |>
mutate(decade = paste0(floor(year / 10) * 10, "s")) |>
group_by(name, decade) |>
summarise(
`Mean Temp (°C)` = round(mean(annual_mean_temp_pop, na.rm = TRUE), 2),
`Deviation (°C)` = round(mean(temp_diff_mean_pop, na.rm = TRUE), 2),
.groups = "drop"
) |>
pivot_wider(
names_from = decade,
values_from = c(`Mean Temp (°C)`, `Deviation (°C)`),
names_glue = "{decade}_{.value}"
) |>
rename(Governorate = name)
datatable(
decade_stats,
options = list(pageLength = 10, dom = 't', scrollX = TRUE),
caption = "Decadal temperature averages showing progression of warming. Deviation is relative to 1960-2010 baseline."
)
Years where temperature exceeded ±2 standard deviations from the baseline are considered extreme events or “climate shocks”.
shock_years <- era5_temp_annual |>
filter(name %in% key_govs) |>
filter(abs(z_score_mean_pop) > 2) |>
mutate(
`Shock Type` = ifelse(z_score_mean_pop > 2, "Heat", "Cold"),
`Z-Score` = round(z_score_mean_pop, 2),
`Deviation (°C)` = round(temp_diff_mean_pop, 2)
) |>
select(Governorate = name, Year = year, `Shock Type`, `Z-Score`, `Deviation (°C)`) |>
arrange(Governorate, desc(Year))
datatable(
shock_years,
options = list(pageLength = 10, scrollX = TRUE),
caption = "Extreme temperature years (±2 SD from baseline). Recent years show more frequent heat shocks."
)
Z-scores normalize temperature deviations by historical variability:
\[Z = \frac{T_{year} - \mu_{baseline}}{\sigma_{baseline}}\]
Where: - \(T_{year}\) is the annual temperature for a given year - \(\mu_{baseline}\) is the mean temperature for 1960-2010 - \(\sigma_{baseline}\) is the standard deviation for 1960-2010
Z-scores beyond ±2 indicate temperatures more than 2 standard deviations from the historical norm, representing exceptional events.
Population-weighted temperatures are calculated by weighting each grid cell’s temperature by its population:
\[T_{weighted} = \frac{\sum_i T_i \times Pop_i}{\sum_i Pop_i}\]
This approach better represents human exposure to temperature changes than simple spatial averages.
All climate data and analysis code are available in the project repository:
Data/intermediate/Governorate Data/era5_temp_19602024.RdsCode/4_EDA/ClimateChange_Report.RmdCode/4_EDA/ClimateChange_ShinyApp.RReport generated on 2026-01-20 using R version 4.5.2